﻿/*
 * Processor
 * 
 * initial implementation : 15.10.2011
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Text;

namespace SourPresser
{



  class Processor
  {
    //  Call this function to remove the key from memory after use for security
    [System.Runtime.InteropServices.DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
    public static extern bool ZeroMemory(IntPtr Destination, int Length);




    public static bool CompressFile(string fileName, SourceFileTypeEnum srcType, string keyFileName)
    {

      //process all ops in memory...
      //source code is usually small chunk of text
      //open input file
      FileStream fi = File.OpenRead(fileName);

      //compress input file
      Stream tmp1 = _compressDeflate(fi);

      //data encryption
      Stream tmp2 = new MemoryStream();
      IEnumerable<byte> keyInfo = _parseKeyFile(keyFileName);
      encryptStreamRijndael(tmp1, ref tmp2, keyInfo);

      //now close original file stream...
      fi.Close();

      //encode data into b64
      string d = _b64Encode(tmp2);

      //create temp file
      string outFile = Path.GetTempFileName();
      fi = File.OpenWrite(outFile);
      StreamWriter w = new StreamWriter(fi, Encoding.UTF8);

      switch (srcType)
      {
        case SourceFileTypeEnum.vbFile:
          w.WriteLine("#if COMPRESSED then");
          w.Write(d);
          w.WriteLine();
          w.WriteLine("#end if");
          break;

        case SourceFileTypeEnum.csFile:
          w.WriteLine("#if COMPRESSED");
          w.Write(d);
          w.WriteLine();
          w.WriteLine("#endif");
          break;

        case SourceFileTypeEnum.xmlFile:
          w.WriteLine("<?xml version='1.0' encoding='utf-8'?><COMPRESSED><![CDATA[");
          w.Write(d);
          w.WriteLine();
          w.WriteLine("]]></COMPRESSED>");
          break;

      }

      w.Flush();
      w.Close();

      File.Copy(outFile, fileName, true);
      File.Delete(outFile);

      return true;

    }




    public static bool DecompressFile(string fileName, SourceFileTypeEnum srcType, string keyFileName)
    {

      //process all ops in memory...

      //open input file
      FileStream fi = File.OpenRead(fileName);
      StreamReader s = new StreamReader(fi, Encoding.UTF8);

      //skip first line
      s.ReadLine();

      List<string> r = new List<string>();

      while( !s.EndOfStream)
      {
        r.Add(s.ReadLine());
      }

      //remove last line
      r.RemoveAt(r.Count - 1);

      //decode from b64
      byte[] data = _b64Decode(String.Concat(r));

      //now close original file stream...
      fi.Close();

      //decrypt file
      IEnumerable<byte> keyInfo = _parseKeyFile(keyFileName);
      Stream tmp1 = new MemoryStream(data);
      Stream tmp2 = new MemoryStream();
      decryptStreamRijndael(tmp1, ref tmp2, keyInfo);

      //decompress input file
      tmp1 = _decompressDeflate(tmp2);

      //create temp file
      string outFile = Path.GetTempFileName();
      fi = File.OpenWrite(outFile);

      tmp1.Position = 0;

      BinaryReader br = new BinaryReader(tmp1);
      BinaryWriter w = new BinaryWriter(fi);
      w.Write(br.ReadBytes((int)tmp1.Length));

      w.Flush();
      w.Close();

      File.Copy(outFile, fileName, true);
      File.Delete(outFile);

      return true;
    }




    private static void encryptStreamRijndael(Stream input, ref Stream output, IEnumerable<byte> keyInfo)
    {
      if (!output.CanWrite)
        throw new ArgumentException("invalid output stream (non writeable)");

      //init cryptoengine
      RijndaelManaged RC = new RijndaelManaged();
      RC.IV = keyInfo.Take(RC.BlockSize / 8).ToArray();
      RC.Key = keyInfo.Skip(RC.IV.Length).ToArray();

      //init input stream
      input.Position = 0;
      BinaryReader br = new BinaryReader(input);
      
      CryptoStream cs = new CryptoStream(output, RC.CreateEncryptor(), CryptoStreamMode.Write);
      //byte[] data = br.ReadBytes((int)input.Length);
      //cs.Write(br.ReadBytes((int)input.Length), 0, (int)input.Length);
      input.CopyTo(cs);
      //cs.CopyTo(output);
      cs.FlushFinalBlock();
      //cs.Close();

    }





    private static void decryptStreamRijndael(Stream input, ref Stream output, IEnumerable<byte> keyInfo)
    {
      if (!output.CanWrite)
        throw new ArgumentException("invalid output stream (non writeable)");

      //init cryptoengine
      RijndaelManaged RC = new RijndaelManaged();
      RC.IV = keyInfo.Take(RC.BlockSize / 8).ToArray();
      RC.Key = keyInfo.Skip(RC.IV.Length).ToArray();

      //init input stream
      input.Position = 0;

      CryptoStream cs = new CryptoStream(input, RC.CreateDecryptor(), CryptoStreamMode.Read);

      cs.CopyTo(output);
      //input.CopyTo(cs);
      //BinaryReader br = new BinaryReader(cs);

      //cs.FlushFinalBlock();

      //byte[] tmp = br.ReadBytes((int)input.Length);

      //output.Write(tmp, 0, tmp.Length);

      cs.Flush();

      //cs.Close();

      //var buffer = new byte[1024 * 1024];
      //int readCount;
      //while ((readCount = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
      //  destStream.Write(buffer, 0, readCount);
      //cryptoStream.Close();
      //destStream.Close();

    }




    private static byte[] _parseKeyFile(string keyFileName)
    {
      var keyData = File.ReadAllBytes(keyFileName);
      var algNameSize = BitConverter.ToInt32(keyData, 0);
      //var algorithmName = Encoding.UTF8.GetString(keyData, 4, algNameSize);
      return keyData.Skip(4 + algNameSize).ToArray();

    }




    private static string _b64Encode(string data)
    {
      try
      {
        //convert string from UTF8 to byte[]
        byte[] encData_byte = new byte[data.Length];
        encData_byte = System.Text.Encoding.UTF8.GetBytes(data);

        return _b64Encode(encData_byte);
      }
      catch (Exception e)
      {
        throw new Exception("Error in base64Encode" + e.Message);
      }
    }




    private static string _b64Encode(byte[] data)
    {
      return Convert.ToBase64String(data);
    }





    private static string _b64Encode(Stream data)
    {
      data.Position = 0;
      BinaryReader br = new BinaryReader(data);
      return Convert.ToBase64String(br.ReadBytes((int)data.Length), 0, (int)data.Length, Base64FormattingOptions.InsertLineBreaks);
    }




    //private static string _b64Decode(string data)
    //{
    //  try
    //  {
    //    System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
    //    System.Text.Decoder utf8Decode = encoder.GetDecoder();

    //    byte[] todecode_byte = Convert.FromBase64String(data);
    //    int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
    //    char[] decoded_char = new char[charCount];
    //    utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
    //    return new String(decoded_char);
    //  }
    //  catch (Exception e)
    //  {
    //    throw new Exception("Error in base64Decode" + e.Message);
    //  }
    //}




    private static byte[] _b64Decode(string data)
    {
        return Convert.FromBase64String(data);
    }





    private static Stream _compressDeflate(Stream input)
    {
      MemoryStream m = new MemoryStream(); //out stream
      DeflateStream d = new DeflateStream(m, CompressionMode.Compress, true);

      // Copy the source file into 
      // the compression stream.
      input.CopyTo(d);

      //close input stream (forces deflate to flush all compressed data)
      d.Close();

      return m;
    }





    private static Stream _decompressDeflate(Stream input)
    {
      MemoryStream outStr = new MemoryStream();

      input.Position = 0;
      DeflateStream d = new DeflateStream(input, CompressionMode.Decompress, true);
      d.CopyTo(outStr);   

      return outStr;
    }





  }

}